package com.jarvis.core.plugin;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.google.common.base.Preconditions;
import com.jarvis.core.interceptor.ConstructorInterceptor;
import com.jarvis.core.interceptor.DeclaredInstanceMethodInterceptor;
import com.jarvis.core.interceptor.Interceptor;
import com.jarvis.core.interceptor.StaticMethodsInterceptor;
import java.util.Objects;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.matcher.ElementMatcher;

/**
 * @author xuxukang
 * @date 2019-07-04 11:24
 */
public abstract class AbstractPluginDefine implements PluginDefine {

  private static final Log log = LogFactory.get();

  @Override
  public ElementMatcher<? super TypeDescription> ignore() {
    return nameStartsWith("net.bytebuddy.")
        .or(nameStartsWith("org.slf4j."))
        .or(nameStartsWith("com.jarvis.core"))
        .or(nameStartsWith("org.groovy."))
        .or(nameContains("javassist"))
        .or(nameContains(".asm."))
        .or(nameContains(".reflectasm."))
        .or(nameStartsWith("sun.reflect"))
        .or(isSynthetic());
  }

  @Override
  public Builder<?> define(TypeDescription typeDescription, Builder<?> builder,
    ClassLoader classLoader) {
    log.info("【class】{}【info】{}", this.getClass().getSimpleName(),
      "define plugin for " + typeDescription.getSimpleName());
    if (ArrayUtil.isEmpty(interceptors())) {
      return builder;
    }

    for (Interceptor interceptor : interceptors()) {
      Preconditions.checkArgument(Objects.nonNull(interceptor.getInterceptClass()));

      ElementMatcher<MethodDescription> elementMatcher = interceptor.methodMatcher();
      if (interceptor instanceof DeclaredInstanceMethodInterceptor) {
        elementMatcher = isDeclaredBy(typeDescription).and(elementMatcher);
      }
      if (interceptor instanceof ConstructorInterceptor) {
        elementMatcher = isConstructor().and(elementMatcher);
      }
      if (interceptor instanceof StaticMethodsInterceptor) {
        elementMatcher = isStatic().and(elementMatcher);
      }

      builder = builder.visit(
          Advice.to(interceptor.getInterceptClass()).on(elementMatcher));
    }

    return builder;
  }

  /**
   * 获取方法拦截器
   */
  public abstract Interceptor[] interceptors();

}
